#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import time
from hashlib import md5
from ConfigParser import ConfigParser
from PathLenRecover import RecoverINI

__author__ = "knkd"
__version__ = "2010.11.03-01.G"

ENCODING = sys.stdout.encoding # Визначаємо кодування терміналу.

def uprint(strk, enc=ENCODING):
    """Функція призначена для переведення кодування
    у зрозуміле для поточнго терміналу."""
    return unicode(strk, 'utf-8').encode(enc)

class FileInfo:
    """Клас зберігає інформацію про файл."""
    def __md5(self):
        file = open(self.fullName, 'rb')
        check = md5()
        while True:
            buffer = file.read(65536)
            if not buffer: break
            check.update(buffer)
        self.hash = check.hexdigest()
    
    def __init__(self, fullName, conf, isLong=None):
        if (os.path.isdir(fullName)):
            self.isDir = '+'
            self.fullName = fullName + os.path.sep
            self.fileSize   = "<DIR>"
            self.parentPath = self.fullName
            self.relName = ""
            self.lastParentDir = ""
            self.hash = None
            self.number = ''
            self.is_duble = ' '
        elif (os.path.isfile(fullName)):
            self.fullName = fullName
            self.is_duble = ' '
            if conf.writeHash:
                self.__md5()
                (self.number, is_duble) = conf.get_number(self.hash)
                if is_duble:
                    self.is_duble = '*'
            else:
                self.number = None
                self.hash = None
            
            self.isDir = '-'
            self.fileSize = str(os.path.getsize(fullName))
            self.parentPath, self.relName = os.path.split(fullName)
            try:
                self.lastParentDir = os.path.split(self.parentPath)[1]
            except:
                self.lastParentDir = self.parentPath + os.path.sep
            self.parentPath = self.parentPath + os.path.sep
            
        self.modifTime = time.strftime("%Y.%m.%d_%H:%M",time.localtime(os.path.getmtime(fullName)))
        self.lenName   = len(self.fullName)
        self.isLong    = isLong


class WriteSolidFile:
    """Клас призначений для запису суцільного файлу."""
    def __init__(self, solidFile):
        """Клас ініціалізується іменем файлу."""
        if os.path.isfile(solidFile):os.remove(solidFile);
        try:
            self.__solidFile = open(solidFile, 'a')
        except:
            print uprint(" Файл {file} в настоящее время недоступен для записи.\n"
                         " Скорее всего он открыт в каком-нибудь редакторе "\
                         "(EXCEL например) и защищён\n"\
                         " от записи. Попробуйте закрыть файл и повторить "\
                         "запуск программы."\
                         "".format(file=solidFile))
            sys.exit()
    
    def write(self, String):
        """Метод для запису строки у файл."""
        self.__solidFile.write(String)
    
    def close(self):
        """Метод призначено для закривання файла
        після закінчення роботи із ним."""
        self.__solidFile.close()


class WriteSplitFile:
    """Клас призначено для запису розділеного файлу."""
    def __init__(self, splitFile, maxLineCount):
        """Клас ініціалізується іменем вихідного файлу
        та максимальною кількістю строк у ньому."""
        print splitFile, maxLineCount
        if os.path.isfile(splitFile): os.remove(splitFile);
        self.__splitFile = None
        self.counter = 0
        self.currentCounter = 0
        self.partCounter = 0
        self.maxLineCount = maxLineCount
        self.startFileName = splitFile
        self.nameList = []
        
    def write(self, String):
        """Метод записує строки у файл, та створює новий файл
        у випадку коли кількість строк у старому максимальна."""
        if (self.currentCounter >= self.maxLineCount) or (self.__splitFile == None):
            if (self.__splitFile != None): self.close()
            self.currentCounter = 0
            currentName = self.get_next_name()
            self.__splitFile = open(currentName, 'w')
            self.partCounter += 1
            self.nameList.append(currentName)
            
        self.__splitFile.write(String)
        self.currentCounter += 1
        self.counter += 1
    
    def get_next_name(self):
        """Метод повертає наступне ім'я для файлу."""
        name, ext = os.path.splitext(self.startFileName)
        counter = str(self.partCounter).rjust(3, "0")
        new_name = "{NAME}_{COUNTER}{EXT}"\
                   "".format(NAME=name, EXT=ext, COUNTER=counter)
        return new_name
    
    def close(self):
        """Метод призначено для закривання файла
        після закінчення роботи із ним."""
        self.__splitFile.close()


class Writer:
    """Клас є інтерфейсом для записуючих класів.
    Згідно з параметрами налаштування визначає потрібність створення
    файлів звіту, та пересилає строки звіту відповідним класам."""
    __solidTXT   = None
    __solidCSV   = None
    __splitCSV   = None
    
    def __init__(self, conf, solidTXT=None, solidCSV=None, splitCSV=None):
        """Клас ініціалізується посиланням на клас налаштуваннь
        та іменами файлів звіту."""
        self.__conf       = conf
        self.__console    = sys.stdout
        self._printCount  = 10000
        self.printCounter = 0
        self.fullCounter  = 0
        self.writeCounter = 0
        self.__bar = None
        if (solidTXT != None): self.__solidTXT = WriteSolidFile(solidTXT)
        if (solidCSV != None): self.__solidCSV = WriteSolidFile(solidCSV)
        if (splitCSV != None): self.__splitCSV = WriteSplitFile(splitCSV, conf.maxLineCount)
    
    def write(self, fileInfo):
        """Метод отримує файловий об'єкт та передає його відповідним класам.
        За допомогою методу reporter(self) друкує короткий звіт на консоль."""
        if (fileInfo.lenName >= self.__conf.minLen):
            txt, csv = self.StringFormatter(fileInfo)
            if (self.__solidTXT != None): self.__solidTXT.write(txt)
            if (self.__solidCSV != None): self.__solidCSV.write(csv)
            if (self.__splitCSV != None): self.__splitCSV.write(csv)
            self.writeCounter += 1
        self.fullCounter += 1
        self.reporter()

    def reporter(self):
        """За допомогою класу ProgressBar
        виводить кількість оброблених файлів."""
        if (self.__bar == None):
            self.__bar = ProgressBar(len100=self._printCount,
                                     LEN=65,
                                     cursor='\x10',
                                     start_count=self.fullCounter)
        self.printCounter += 1
        self.__bar.touch()
        
        if (self.printCounter == self._printCount):
            message = uprint("\r{spase}\r Обработано файлов: {count}\n"\
                             "".format(spase=" "*79,
                                       count=str(self.fullCounter).rjust(6)))
            self.__console.write(message)
            self.printCounter = 0
            self.__bar = None
    
    def close(self):
        """Метод призначено для закривання файлів
        після закінчення роботи із ними."""
        if (self.__solidTXT != None): self.__solidTXT.close()
        if (self.__solidCSV != None): self.__solidCSV.close()
        if (self.__splitCSV != None):
            self.__splitCSV.close()
            self.splitNameList = self.__splitCSV.nameList
        else:
            self.splitNameList = ''
    
    def StringFormatter(self, fileInfo):
        """Метод формує строки для звітів."""
        if (fileInfo.isLong == None):
            isLong = " "
        else:
            isLong = fileInfo.isLong
        
        lenName = str(fileInfo.lenName)
        isDir = fileInfo.isDir
        fileSize = str(fileInfo.fileSize)
        modifTime = fileInfo.modifTime
        fullName = fileInfo.fullName
        parentName = fileInfo.parentPath
        lastParentDir = fileInfo.lastParentDir
        relName = fileInfo.relName
        hash = fileInfo.hash if fileInfo.hash else " "*32
        number = fileInfo.number
        is_duble = fileInfo.is_duble
        
        try:
            if self.__conf.writeHash:
                patternTXT = self.__conf.patternTXTmd5
            else:
                patternTXT = self.__conf.patternTXT
            
            txt = patternTXT.format(ISLONG=isLong, LEN=lenName,
                                    SIZE=fileSize, DATE=modifTime,
                                    NAME=fullName, ISDIR=isDir,
                                    HASH=hash, NUM=number,
                                    ISDUBLE=is_duble)
            
            if self.__conf.writeHash:
                patternCSV = self.__conf.patternCSVmd5
            else:
                patternCSV = self.__conf.patternCSV
            
            csv = patternCSV.format(ISLONG=isLong, LEN=lenName,
                                    SIZE=fileSize, DATE=modifTime,
                                    PATH=parentName, NAME=relName,
                                    ISDIR=isDir, LAST=lastParentDir,
                                    FULL=fullName, HASH=hash,
                                    NUM=number, ISDUBLE=is_duble)
        except Exception,E:
            raise E
            print uprint("\n Содержимое файла с шаблонами, некорректно. "\
            "Программа завершает работу.\n Удалите файл шаблона и он будет "\
            "восстановлен при следующем запуске.")
            sys.exit()
        return (txt, csv)

class ProgressBar:
    """Клас призначено для виводу на консоль прогрес-бару."""
    def __init__(self, space_filler=' ', 
                 barel_filler='=', LEN=25,
                 len100=1000, cursor='\x01',
                 start_count=0):
        self.space_filler = space_filler
        self.barel_filler = barel_filler
        self.LEN = LEN
        self.len100 = len100
        self.count = 0
        self.percent = 0
        self.console = sys.stdout
        self.bar = 0
        self.cursor = cursor
        self.start_count = start_count
        
    def touch(self, step=1):
        self.count += step
        if (self.count > self.len100):
            return
        bar = self.bar
        percent = self.percent
        self.percent = int((1 - (self.len100 - self.count)/float(self.len100))*100)
        self.bar     = int((1 - (self.len100 - self.count)/float(self.len100))*self.LEN)
        if (self.percent > 1):
            if (bar < self.bar) or (percent < self.percent):
                count = self.start_count + self.count
                if (self.bar == self.LEN - 1): self.cursor = self.barel_filler
                self.pprint(count)
    
    def pprint(self, count):
        res =  "\r{REV}\r[{BAR}] {PROC:6d}".format(
                        REV=''.ljust(79, ' '),
                        
                        BAR=''.ljust(self.bar - 1,self.barel_filler)\
                        + self.cursor \
                        + ''.ljust( (self.LEN - self.bar),   self.space_filler),
                        
                        PROC=count)
        self.console.write(uprint(res))
        self.console.flush()

class Config:
    """Клас отримує та зберігає налаштування."""
    solidTxtName = None
    solidCsvName = None
    splitCsvName = None
    
    def get_number(self, hash):
        if hash in self.hashlist:
            res  = self.hashlist.index(hash)
            is_duble = True
        else:
            is_duble = False
            res = self.counter
            self.counter += 1
            self.hashlist.append(hash)
        return (res, is_duble)
    
    def __init__(self, scriptAbsPath):
        self.counter = 0
        self.hashlist = []
        
        pathName, scriptName = os.path.split(scriptAbsPath)
        configName = scriptName.split('.')[0] + ".ini"
        patternName = scriptName.split('.')[0] + "_Pattern.ini"
        
        configAbsPath = os.path.join(pathName, configName)
        patternAbsPath = os.path.join(pathName, patternName)
        
        recover = RecoverINI([configAbsPath, patternAbsPath])
        
        if not os.path.isfile(configAbsPath): recover.recoverINI()
        if not os.path.isfile(patternAbsPath): recover.recoverPattern()
            
        conf = ConfigParser()
        
        try:
            conf.read(configAbsPath)
        except:
            print uprint("\nСодержимое файла {0} с настройками, некорректно. "\
            "Программа завершает работу.".format(configAbsPath))
            sys.exit()
        
        try:
            patternContent     = open(patternAbsPath).readlines()[1:5]
            # print ''.join(patternContent)
            self.patternTXT    = patternContent[0]
            self.patternTXTmd5 = patternContent[1]
            self.patternCSV    = patternContent[2]
            self.patternCSVmd5 = patternContent[3]
        except:
            print uprint("\nСодержимое файла {0} с шаблонами, некорректно. "\
            "Программа завершает работу.".format(patternAbsPath))
            sys.exit(1)
        
        if (conf.getint("SolidTXT", "Write") == 1):
            self.solidTxtName = conf.get("SolidTXT", "Name")
        if (conf.getint("SolidCSV", "Write") == 1):
            self.solidCsvName = conf.get("SolidCSV", "Name")
        if (conf.getint("SplitCSV", "Write")):
            self.splitCsvName = conf.get("SplitCSV", "StartName")
        
        self.maxLineCount = conf.getint("SplitCSV", "MaxCount")
        self.minLen = conf.getint("Main", "MinLen")
        
        if conf.getint("Main", "WriteHash") == 1:
            self.writeHash = True
        else:
            self.writeHash = False
